﻿//
// SMS PocketPC Proxy by Roberto Freato
// http://www.codeplex.com/SMSPPP/
// http://www.robertofreato.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so. subject to
// the following conditions:
// 
// 1. The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.
// 2. Namespaces or Class names of the Software components shall not
//    be modified.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Xml.Serialization;
using OCDStudio.SMSPPP.Common.Mobile;


namespace OCDStudio.SMSPPP.Common
{
    /// <remarks>
    /// Class used to manage the Remote connection with the WindowsMobile powered device.
    /// It uses a multiple set of configuration settings to meet the needs of mobility of a typical
    /// mobile device.
    /// <example>
    /// This example shows how to get an instance of MobileConnection. This should work fine if 
    /// you already have configured the default user connection settings.
    /// <code>
    /// MobileConnection conn = MobileConnectionFactory.GetInstance();
    /// conn.ConnectToDevice(); 
    /// </code> 
    /// </example>
    /// <seealso cref="MobileConnectionProperties"/>
    /// </remarks>
    public class MobileConnection
    {

        #region "Fields"

        private bool isActive;
        private MobileConnectionProperties properties;

        public MobileConnectionProperties Properties
        {
            get { return properties; }
            set { properties = value; }
        }
        private ConnectionProperties activeProperties;

        #endregion

        #region "Properties"

        /// <summary>
        /// This property shows if there is a connection active at the moment of call.
        /// </summary>
        public bool IsActive
        {
            get { return isActive; }
        }

        #endregion

        #region "Constructors"

        /// <summary>
        /// This constructor allows the ConnectionFactory to get an instance based of a set
        /// fo MobileConnectionProperties. It is not visible externally for pattern strategy reason.
        /// </summary>
        /// <param name="properties">The object representing the set of configuration setting.</param>
        internal MobileConnection(MobileConnectionProperties properties)
        {
            this.properties = properties;
        }

        #endregion

        #region "Public Methods"

        /// <summary>
        /// This method allows you to connect to remote device. It supposes that you have a valid set
        /// of user configuration settings. If you do not have, or if there are not valid endpoint, an exception
        /// will be thrown.
        /// </summary>
        /// <exception cref="InvalidUserConnectionPropertiesException">
        /// Thrown by the method when the connection does not have a valid set of configuration setting or
        /// when there are not valid endpoint.
        /// </exception>
        /// <seealso cref="MobileConnectionProperties"/>
        public void ConnectToDevice()
        {
            while (properties.MoveNext())
            {
                activeProperties = properties.Current as ConnectionProperties;
                try
                {
                    SMSPPPCommandsResponses response = SendCommandAndWaitResponse(SMSPPPCommands.HEARTBEAT, new SMSPPPCommandBody());
                    if (response == SMSPPPCommandsResponses.HEARTBEAT_OK)
                    {
                        properties.Reset();
                        isActive = true;
                        return;
                    }
                }
                catch
                {
                    continue;
                }

            }
            properties.Reset();
            throw new InvalidUserConnectionPropertiesException();
        }



        /// <summary>
        /// This method is the entry point for every command that has to be sent to the remote device.
        /// It uses enums for request codes and response codes.
        /// <example>
        /// This example shows a typical command invocation.
        /// <code>
        /// SMSPPPCommandBody body=new SMSPPPCommandBody();
        /// body.Parameters.Add(new NameValue(recipient,message));
        /// SMSPPPCommandsResponses resp=conn.SendCommandAndWaitResponse(
        ///     OCDStudio.SMSPPP.Common.Mobile.SMSPPPCommands.SEND_SMS, body);
        /// </code>
        /// </example>
        /// </summary>
        /// <param name="command">The command name representing the remote action</param>
        /// <param name="body">The optional body of the message</param>
        /// <returns>The status code of the executed command.</returns>
        /// <exception cref="ConnectionNotInitializedException">
        /// Raised when you attempt to send a command without activate the connection first.
        /// </exception>
        public SMSPPPCommandsResponses SendCommandAndWaitResponse(SMSPPPCommands command, SMSPPPCommandBody body)
        {
            if (!isActive && command!=SMSPPPCommands.HEARTBEAT) throw new ConnectionNotInitializedException();

            TcpClient cl = new TcpClient();
            IAsyncResult result = cl.BeginConnect(activeProperties.Address, activeProperties.Port, null, null);

            bool success = result.AsyncWaitHandle.WaitOne(3000, true);

            if (!success)
            {                
                
                throw new ApplicationException("Failed to connect server.");
            }
            cl.Close();

            cl = new TcpClient();
            cl.Connect(activeProperties.Address, activeProperties.Port);
            StreamWriter wr = new StreamWriter(cl.GetStream());
            StreamReader rd = new StreamReader(cl.GetStream());

            XmlSerializer s = new XmlSerializer(typeof(SMSPPPCommandBody));
            MemoryStream str = new MemoryStream();
            s.Serialize(str, body);
            str.Seek(0, SeekOrigin.Begin);
            StreamReader reader = new StreamReader(str);
            string message = reader.ReadToEnd().Replace("\r\n", "");

            wr.WriteLine(command + SMSPPPConstants.COMMAND_BODY_SEPARATOR + message);
            wr.Flush();
            string resp = rd.ReadLine();
            SMSPPPCommandsResponses response = (SMSPPPCommandsResponses)Enum.Parse(typeof(SMSPPPCommandsResponses),
                resp, true);
            cl.Close();
            return response;
        }

        #endregion

    }

    #region "Exceptions"

    /// <remarks>
    /// Thrown by a method when the connection does not have a valid set of configuration setting or
    /// when there are not valid endpoint.
    /// </remarks>
    public class InvalidUserConnectionPropertiesException : Exception
    {

        public override string Message
        {
            get
            {
                return "Your connection properties are invalid or corrupted. Please use the ClearUserProperties() method to restore defaults.";
            }
        }

    }

    /// <remarks>
    /// Raised when you attempt to send a command without activate the connection first.
    /// </remarks>
    public class ConnectionNotInitializedException : Exception
    {

        public override string Message
        {
            get
            {
                return "Mobile connection is not inizialized. Use the GetInstance method instead.";
            }
        }

    }

    #endregion

    
}
